import pyautogui
import time
import xlrd
import pyperclip
import keyboard  # 添加keyboard库
import ctypes, sys
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
from datetime import datetime
import re
import os
import importlib.util

#定义鼠标事件

#pyautogui库其他用法 https://blog.csdn.net/qingfengxd1/article/details/108270159

INPUT_METHOD = 3 # 改为 2 或 3 尝试不同的输入方法
INPUT_DELAY = 0.5  # 如果输入不稳定，可以改大这个值，比如 1.0

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

if not is_admin():
    print("建议使用管理员权限运行此程序")

# 检查必要的依赖
def check_dependencies():
    missing_deps = []
    version_conflicts = []
    
    try:
        import numpy as np
        if np.__version__.startswith('2.'):
            version_conflicts.append(f"numpy {np.__version__} (需要降级到 1.x 版本)")
    except ImportError:
        missing_deps.append("numpy<2.0.0")
    
    # 检查 OpenCV
    try:
        import cv2
    except ImportError:
        missing_deps.append("opencv-python==4.8.0.74")
    except Exception as e:
        if "numpy" in str(e).lower():
            version_conflicts.append("numpy 版本不兼容 (需要降级到 1.x 版本)")
    
    # 检查 pywin32（只要能 import win32api/win32gui 就算通过）
    try:
        import win32api
        import win32gui
    except ImportError:
        missing_deps.append("pywin32>=300")
    
    # 检查其他依赖
    required_deps = {
        "pyautogui": "pyautogui==0.9.54",
        "keyboard": "keyboard==0.13.5",
        "xlrd": "xlrd==2.0.1",
        "PIL": "pillow==10.0.0"
    }
    
    for module, package in required_deps.items():
        try:
            importlib.import_module(module)
        except ImportError:
            missing_deps.append(package)
    
    return missing_deps, version_conflicts

def mouseClick(clickTimes, lOrR, img, reTry, stop_event=None):
    """执行鼠标点击操作
    Args:
        clickTimes: 点击次数
        lOrR: 左键还是右键
        img: 要点击的图片
        reTry: 重试策略
        stop_event: 停止事件
    Returns:
        bool: 是否成功执行
    """
    try:
        # 构建图片完整路径
        img_path = os.path.join('images', img)
        if not os.path.exists(img_path):
            raise FileNotFoundError(f"图片文件不存在: {img_path}")

        # 检查是否安装了OpenCV
        has_opencv = importlib.util.find_spec("cv2") is not None
        
        if reTry == 1:
            retry_count = 0
            max_retries = 50  # 最大重试次数
            while retry_count < max_retries:
                if stop_event and stop_event.is_set():
                    return False
                    
                try:
                    if has_opencv:
                        location = pyautogui.locateCenterOnScreen(img_path, confidence=0.8)
                    else:
                        location = pyautogui.locateCenterOnScreen(img_path)
                        
                    if location is not None:
                        pyautogui.click(location.x, location.y, clicks=clickTimes, interval=0.2, duration=0.2, button=lOrR)
                        print(f"成功点击图片 {img}")
                        return True
                    print(f"未找到匹配图片 {img}, 0.1秒后重试 ({retry_count + 1}/{max_retries})")
                    time.sleep(0.1)
                    retry_count += 1
                except Exception as e:
                    if "confidence" in str(e):
                        print("警告: OpenCV未安装，将使用精确匹配模式")
                        has_opencv = False
                        continue
                    raise
            raise TimeoutError(f"超过最大重试次数({max_retries})，未能找到图片: {img}")

        elif reTry == -1:
            while True:
                if stop_event and stop_event.is_set():
                    return False
                    
                try:
                    if has_opencv:
                        location = pyautogui.locateCenterOnScreen(img_path, confidence=0.8)
                    else:
                        location = pyautogui.locateCenterOnScreen(img_path)
                        
                    if location is not None:
                        pyautogui.click(location.x, location.y, clicks=clickTimes, interval=0.2, duration=0.2, button=lOrR)
                        print(f"成功点击图片 {img}")
                        return True
                    print(f"未找到匹配图片 {img}, 0.1秒后重试")
                    time.sleep(0.1)
                except Exception as e:
                    if "confidence" in str(e):
                        print("警告: OpenCV未安装，将使用精确匹配模式")
                        has_opencv = False
                        continue
                    raise

        elif reTry > 1:
            i = 1
            while i < reTry + 1:
                if stop_event and stop_event.is_set():
                    return False
                    
                try:
                    if has_opencv:
                        location = pyautogui.locateCenterOnScreen(img_path, confidence=0.8)
                    else:
                        location = pyautogui.locateCenterOnScreen(img_path)
                        
                    if location is not None:
                        pyautogui.click(location.x, location.y, clicks=clickTimes, interval=0.2, duration=0.2, button=lOrR)
                        print(f"第 {i} 次重复点击图片 {img} 成功")
                        i += 1
                        if i > reTry:
                            return True
                    time.sleep(0.1)
                except Exception as e:
                    if "confidence" in str(e):
                        print("警告: OpenCV未安装，将使用精确匹配模式")
                        has_opencv = False
                        continue
                    raise
            return False
    except Exception as e:
        print(f"点击操作失败: {str(e)}")
        raise

def input_text_safely(text):
    """更可靠的文本输入方法"""
    print(f"准备输入文本: {text}")
    
    # 先清空当前内容
    keyboard.press_and_release('ctrl+a')
    time.sleep(0.2)
    keyboard.press_and_release('backspace')
    time.sleep(0.2)
    
    # 尝试直接输入
    try:
        keyboard.write(text, delay=0.1)
        print(f"完成输入: {text}")
    except Exception as e:
        print(f"输入出错: {str(e)}")
        return False
    
    time.sleep(0.2)
    keyboard.press_and_release('enter')
    return True

# 数据检查
# cmdType.value  1.0 左键单击    2.0 左键双击  3.0 右键单击  4.0 输入  5.0 等待  6.0 滚轮
# ctype     空：0
#           字符串：1
#           数字：2
#           日期：3
#           布尔：4
#           error：5
def dataCheck(sheet1):
    checkCmd = True
    # 行数检查
    if sheet1.nrows < 2:
        print("没数据啊哥")
        checkCmd = False
    # 每行数据检查
    i = 1
    while i < sheet1.nrows:
        # 第1列 操作类型检查
        cmdType = sheet1.row(i)[0]
        if cmdType.ctype != 2 or (cmdType.value not in [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]):
            print(f'第{i+1}行,第1列数据有毛病，值为：{cmdType.value}，类型为：{cmdType.ctype}')
            checkCmd = False
        # 第2列 内容检查
        cmdValue = sheet1.row(i)[1]
        # 读图点击类型指令，内容必须为字符串类型
        if cmdType.value in [1.0, 2.0, 3.0, 7.0]:
            if cmdValue.ctype != 1:
                print(f'第{i+1}行,第2列数据有毛病（应为图片名字符串），值为：{cmdValue.value}，类型为：{cmdValue.ctype}')
                checkCmd = False
        # 输入类型，内容不能为空
        if cmdType.value == 4.0:
            if cmdValue.ctype == 0:
                print(f'第{i+1}行,第2列数据有毛病（输入内容不能为空），值为：{cmdValue.value}，类型为：{cmdValue.ctype}')
                checkCmd = False
        # 等待类型，内容必须为数字
        if cmdType.value == 5.0:
            if cmdValue.ctype != 2:
                print(f'第{i+1}行,第2列数据有毛病（等待时间应为数字），值为：{cmdValue.value}，类型为：{cmdValue.ctype}')
                checkCmd = False
        # 滚轮事件，内容必须为数字
        if cmdType.value == 6.0:
            if cmdValue.ctype != 2:
                print(f'第{i+1}行,第2列数据有毛病（滚轮应为数字），值为：{cmdValue.value}，类型为：{cmdValue.ctype}')
                checkCmd = False
        i += 1
    return checkCmd

def executeCommand(sheet1, i, stop_event=None):
    """执行单个指令的函数
    Args:
        sheet1: Excel工作表
        i: 当前行号
        stop_event: 停止事件
    Returns:
        bool: 是否成功执行
    Raises:
        Exception: 执行出错时抛出异常
    """
    try:
        if stop_event and stop_event.is_set():
            return False
            
        cmdType = sheet1.row(i)[0]
        cmdValue = sheet1.row(i)[1].value
        
        # 7.0 检测目标（不报错，用于循环检测）
        if cmdType.value == 7.0:
            try:
                if not mouseClick(1, "left", cmdValue, 1, stop_event):
                    print(f"未检测到目标图片: {cmdValue}")
                    return False
                print(f"检测到目标图片: {cmdValue}")
                return True
            except Exception as e:
                print(f"检测过程出错: {str(e)}")
                return False
        
        # 1.0 左键单击
        if cmdType.value == 1.0:
            reTry = 1
            if sheet1.row(i)[2].ctype == 2 and sheet1.row(i)[2].value != 0:
                reTry = sheet1.row(i)[2].value
            if not mouseClick(1, "left", cmdValue, reTry, stop_event):
                raise Exception(f"左键单击失败: {cmdValue}")
            return True

        # 2.0 左键双击
        elif cmdType.value == 2.0:
            reTry = 1
            if sheet1.row(i)[2].ctype == 2 and sheet1.row(i)[2].value != 0:
                reTry = sheet1.row(i)[2].value
            if not mouseClick(2, "left", cmdValue, reTry, stop_event):
                raise Exception(f"左键双击失败: {cmdValue}")
            return True

        # 3.0 右键单击
        elif cmdType.value == 3.0:
            reTry = 1
            if sheet1.row(i)[2].ctype == 2 and sheet1.row(i)[2].value != 0:
                reTry = sheet1.row(i)[2].value
            if not mouseClick(1, "right", cmdValue, reTry, stop_event):
                raise Exception(f"右键单击失败: {cmdValue}")
            return True

        # 4.0 输入
        elif cmdType.value == 4.0:
            # 处理数字类型
            if isinstance(cmdValue, (int, float)):
                # 如果是整数或整数形式的浮点数，转换为整数
                if cmdValue.is_integer():
                    cmdValue = str(int(cmdValue))
                else:
                    cmdValue = str(cmdValue)
            else:
                cmdValue = str(cmdValue)
            
            if not input_text_safely(cmdValue):
                raise Exception(f"文本输入失败: {cmdValue}")
            time.sleep(0.5)
            return True

        # 5.0 等待
        elif cmdType.value == 5.0:
            waitTime = float(cmdValue)
            if waitTime < 0:
                raise ValueError(f"等待时间不能为负数: {waitTime}")
            time.sleep(waitTime)
            print(f"等待 {waitTime} 秒")
            return True

        # 6.0 滚轮
        elif cmdType.value == 6.0:
            scroll = int(cmdValue)
            pyautogui.scroll(scroll)
            print(f"滚轮滑动 {scroll} 距离")
            return True

        else:
            raise ValueError(f"未知的命令类型: {cmdType.value}")

    except Exception as e:
        error_msg = f"第 {i + 1} 行指令执行失败: {str(e)}"
        print(error_msg)
        raise Exception(error_msg)

def mainWork(sheet1, loop_mode=1, stop_event=None):
    """执行RPA任务
    Args:
        sheet1: Excel工作表
        loop_mode: 执行模式（1:单次, 2:循环）
        stop_event: 停止事件
    """
    try:
        i = 1
        if loop_mode == 1:
            while i < sheet1.nrows:
                if stop_event and stop_event.is_set():
                    print("收到停止信号，正在停止...")
                    return
                    
                print(f"当前执行第 {i} 行指令")
                
                # 处理跳转信息
                jump_info = sheet1.row(i)[3].value if len(sheet1.row(i)) > 3 and sheet1.row(i)[3].ctype == 1 else None
                if jump_info:
                    try:
                        parts = jump_info.split(',')
                        loop = [part for part in parts if 'loop=' in part]
                        times = [part for part in parts if 'times=' in part]
                        if loop and times:
                            jump_row = int(loop[0].split('=')[1]) - 1
                            total_times = int(times[0].split('=')[1])
                            current_row = i
                            
                            for loop_count in range(total_times):
                                if stop_event and stop_event.is_set():
                                    return
                                    
                                if not executeCommand(sheet1, current_row, stop_event):
                                    return
                                
                                temp_i = jump_row
                                while temp_i < current_row:
                                    if stop_event and stop_event.is_set():
                                        return
                                    if not executeCommand(sheet1, temp_i, stop_event):
                                        return
                                    temp_i += 1
                            i += 1
                            continue
                    except Exception as e:
                        raise Exception(f"跳转指令执行失败: {str(e)}")
                
                # 执行指令
                result = executeCommand(sheet1, i, stop_event)
                
                # 如果是检测指令(7.0)且执行失败，回到第1行重新开始
                if not result and sheet1.row(i)[0].value == 7.0:
                    print("检测失败，重新开始...")
                    i = 1
                    continue
                    
                if not result:
                    return
                i += 1
        
        elif loop_mode == 2:
            while True:
                if stop_event and stop_event.is_set():
                    print("收到停止信号，正在停止...")
                    return
                    
                i = 1
                while i < sheet1.nrows:
                    if stop_event and stop_event.is_set():
                        return
                        
                    print(f"当前执行第 {i} 行指令")
                    
                    # 处理跳转信息
                    jump_info = sheet1.row(i)[3].value if len(sheet1.row(i)) > 3 and sheet1.row(i)[3].ctype == 1 else None
                    if jump_info:
                        try:
                            parts = jump_info.split(',')
                            loop = [part for part in parts if 'loop=' in part]
                            times = [part for part in parts if 'times=' in part]
                            if loop and times:
                                jump_row = int(loop[0].split('=')[1]) - 1
                                total_times = int(times[0].split('=')[1])
                                current_row = i
                                
                                for loop_count in range(total_times):
                                    if stop_event and stop_event.is_set():
                                        return
                                        
                                    if not executeCommand(sheet1, current_row, stop_event):
                                        return
                                    
                                    temp_i = jump_row
                                    while temp_i < current_row:
                                        if stop_event and stop_event.is_set():
                                            return
                                        if not executeCommand(sheet1, temp_i, stop_event):
                                            return
                                        temp_i += 1
                                i += 1
                                continue
                        except Exception as e:
                            raise Exception(f"跳转指令执行失败: {str(e)}")
                    
                    # 执行指令
                    result = executeCommand(sheet1, i, stop_event)
                    
                    # 如果是检测指令(7.0)且执行失败，回到第1行重新开始
                    if not result and sheet1.row(i)[0].value == 7.0:
                        print("检测失败，重新开始...")
                        i = 1
                        continue
                        
                    if not result:
                        return
                    i += 1
                time.sleep(0.1)
                print("等待0.1秒")
    except Exception as e:
        print(f"执行过程出错: {str(e)}")
        raise

def ensure_window_active(window_title):
    try:
        import win32gui
        hwnd = win32gui.FindWindow(None, window_title)
        if hwnd:
            win32gui.SetForegroundWindow(hwnd)
            return True
    except:
        return False

class Logger:
    def __init__(self, widget):
        self.widget = widget
        self.widget.tag_configure('success', foreground='green')
        self.widget.tag_configure('error', foreground='red')
        self.widget.tag_configure('info', foreground='blue')
        self.widget.tag_configure('warning', foreground='orange')

    def format_message(self, message):
        """格式化日志消息"""
        timestamp = datetime.now().strftime("[%H:%M:%S]")
        if "成功" in message:
            return (f"{timestamp} ✓ {message.strip()}\n", 'success')
        elif "错误" in message or "失败" in message:
            return (f"{timestamp} ✗ {message.strip()}\n", 'error')
        elif "警告" in message or "未找到" in message:
            return (f"{timestamp} ⚠ {message.strip()}\n", 'warning')
        elif "当前执行" in message:
            return (f"{timestamp} → {message.strip()}\n", 'info')
        else:
            return None

    def write(self, message):
        if not message.strip():
            return
        
        formatted = self.format_message(message)
        if formatted:
            msg, tag = formatted
            self.widget.insert(tk.END, msg, tag)
            self.widget.see(tk.END)
            self.widget.update()

    def flush(self):
        pass

class RPAUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("金市自动经办RPA机器人v1.0")
        self.geometry("900x600")
        self.stop_event = threading.Event()
        self.timer_thread = None
        self.worker_thread = None
        
        # 检查依赖
        missing_deps, version_conflicts = check_dependencies()
        
        if missing_deps or version_conflicts:
            message = []
            if missing_deps:
                message.append("缺少以下依赖：")
                message.extend(missing_deps)
                message.append("\n请运行以下命令安装依赖：")
                message.append(f"pip install {' '.join(missing_deps)}")
            
            if version_conflicts:
                message.append("\n存在版本冲突：")
                message.extend(version_conflicts)
                message.append("\n请运行以下命令解决版本冲突：")
                message.append("pip install numpy==1.24.3")
                message.append("pip install --upgrade opencv-python==4.8.0.74")
            
            messagebox.showwarning(
                "依赖检查",
                "\n".join(message)
            )
        
        self.create_widgets()
        self.setup_logger()

    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 控制区域（左侧）
        control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=(10, 5))
        control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))

        # 循环模式选择
        mode_frame = ttk.LabelFrame(control_frame, text="执行模式", padding=(5, 5))
        mode_frame.pack(fill=tk.X, padx=5, pady=5)
        
        self.loop_mode = tk.StringVar(value="1")
        ttk.Radiobutton(mode_frame, text="执行一次", variable=self.loop_mode, value="1").pack(anchor=tk.W)
        ttk.Radiobutton(mode_frame, text="循环执行", variable=self.loop_mode, value="2").pack(anchor=tk.W)

        # 定时设置区域
        timer_frame = ttk.LabelFrame(control_frame, text="定时设置", padding=(5, 5))
        timer_frame.pack(fill=tk.X, padx=5, pady=5)

        time_frame = ttk.Frame(timer_frame)
        time_frame.pack(fill=tk.X, pady=5)
        
        self.hour_var = tk.StringVar(value="00")
        self.minute_var = tk.StringVar(value="00")
        
        ttk.Label(time_frame, text="时:").pack(side=tk.LEFT)
        self.hour_entry = ttk.Entry(time_frame, width=3, textvariable=self.hour_var)
        self.hour_entry.pack(side=tk.LEFT, padx=2)
        
        ttk.Label(time_frame, text="分:").pack(side=tk.LEFT)
        self.minute_entry = ttk.Entry(time_frame, width=3, textvariable=self.minute_var)
        self.minute_entry.pack(side=tk.LEFT, padx=2)

        self.timer_status = ttk.Label(timer_frame, text="未设置定时", foreground="gray")
        self.timer_status.pack(pady=5)

        # 按钮区域
        button_frame = ttk.Frame(control_frame)
        button_frame.pack(pady=10)
        
        style = ttk.Style()
        style.configure('Start.TButton', foreground='green')
        style.configure('Stop.TButton', foreground='red')
        style.configure('Timer.TButton', foreground='blue')

        self.start_button = ttk.Button(button_frame, text="立即执行", style='Start.TButton', 
                                     command=self.start_execution)
        self.start_button.pack(pady=5)
        
        self.timer_button = ttk.Button(button_frame, text="设置定时", style='Timer.TButton',
                                     command=self.set_timer)
        self.timer_button.pack(pady=5)
        
        self.stop_button = ttk.Button(button_frame, text="停止执行", style='Stop.TButton',
                                    command=self.stop_execution, state=tk.DISABLED)
        self.stop_button.pack(pady=5)

        # 添加联系信息标签
        contact_frame = ttk.Frame(control_frame)
        contact_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=10)
        
        contact_label = ttk.Label(
            contact_frame, 
            text="有问题OA联系13516-徐利鹏",
            foreground="gray",
            font=("微软雅黑", 9)
        )
        contact_label.pack(side=tk.LEFT)

        # 日志区域（右侧）
        log_frame = ttk.LabelFrame(main_frame, text="执行日志", padding=(10, 5))
        log_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        # 先放清除日志按钮，再放日志框，使按钮始终可见
        ttk.Button(log_frame, text="清除日志", command=self.clear_log).pack(pady=5)
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, width=50, height=30)
        self.log_text.pack(fill=tk.BOTH, expand=True)

    def setup_logger(self):
        """设置日志重定向"""
        sys.stdout = Logger(self.log_text)

    def clear_log(self):
        """清除日志内容"""
        self.log_text.delete(1.0, tk.END)

    def validate_time(self, hour, minute):
        """验证时间格式"""
        try:
            hour = int(hour)
            minute = int(minute)
            return 0 <= hour <= 23 and 0 <= minute <= 59
        except ValueError:
            return False

    def set_timer(self):
        """设置定时任务"""
        hour = self.hour_var.get()
        minute = self.minute_var.get()
        
        if not self.validate_time(hour, minute):
            messagebox.showerror("错误", "请输入有效的时间（时：0-23，分：0-59）")
            return

        try:
            target_time = datetime.now().replace(hour=int(hour), minute=int(minute), second=0, microsecond=0)
            if target_time < datetime.now():
                target_time = target_time.replace(day=target_time.day + 1)

            if self.timer_thread and self.timer_thread.is_alive():
                self.timer_thread.cancel()

            delta = (target_time - datetime.now()).total_seconds()
            self.timer_thread = threading.Timer(delta, self.start_execution)
            self.timer_thread.start()

            self.timer_status.config(text=f"定时任务：{target_time.strftime('%H:%M')}", foreground="green")
            messagebox.showinfo("成功", f"定时任务已设置，将在 {target_time.strftime('%H:%M')} 执行")
        except Exception as e:
            messagebox.showerror("错误", f"设置定时任务失败: {str(e)}")

    def start_execution(self):
        """开始执行RPA任务"""
        try:
            self.start_button.config(state=tk.DISABLED)
            self.timer_button.config(state=tk.DISABLED)
            self.stop_button.config(state=tk.NORMAL)
            self.stop_event.clear()

            print("开始执行RPA任务...")
            file = 'cmd.xls'
            wb = xlrd.open_workbook(filename=file)
            sheet1 = wb.sheet_by_index(0)
            
            if dataCheck(sheet1):
                loop_mode = int(self.loop_mode.get())
                worker_thread = threading.Thread(target=self.run_rpa, args=(sheet1, loop_mode))
                worker_thread.daemon = True
                worker_thread.start()
            else:
                messagebox.showerror("错误", "数据检查未通过！")
                self.reset_buttons()
        except Exception as e:
            messagebox.showerror("错误", f"启动失败: {str(e)}")
            self.reset_buttons()

    def run_rpa(self, sheet1, loop_mode):
        """在新线程中运行RPA任务"""
        try:
            mainWork(sheet1, loop_mode, self.stop_event)
        except Exception as e:
            self.after(0, lambda: messagebox.showerror("错误", str(e)))
        finally:
            self.reset_buttons()

    def stop_execution(self):
        """停止执行RPA任务"""
        self.stop_event.set()
        if self.timer_thread and self.timer_thread.is_alive():
            self.timer_thread.cancel()
            self.timer_status.config(text="定时已取消", foreground="gray")
        messagebox.showinfo("提示", "正在停止...")
        self.reset_buttons()

    def reset_buttons(self):
        """重置按钮状态"""
        self.start_button.config(state=tk.NORMAL)
        self.timer_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)

if __name__ == '__main__':
    app = RPAUI()
    app.mainloop()
